package space.edge.d.demo.qrcode.privatecode.ui;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;

import space.edge.d.demo.qrcode.common.BitArray;
import space.edge.d.demo.qrcode.privatecode.common.Point;
import space.edge.d.demo.qrcode.privatecode.encoder.Code;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by gsl on 2017/10/12.
 * 绘制圆形码
 */

public class CodeRenderer {


    public static final int INIT_RADIUS = 155;
    public static final int NUMBER_OF_TURNS = 8;
    public static final int[] PER_CIRCLE_POINT_COUNT = {81, 91, 102, 112, 123, 133, 143, 154};
    public static final int[][] SKIPPED_POINT = {{43, 50}, {14, 20, 46, 55, 80, 87, 114, 120}, {15, 22, 49, 59, 86, 93, 122, 129}, {16, 23, 53, 64, 93, 100, 132, 138}};
    public static final int CODE_SIZE = 600;
    private static final int FINDER_PATTERN_SIZE = 50;
    private static final Point[] FINDER_PATTERN_POSITION = {new Point(105, 105), new Point(495, 105), new Point(105, 495), new Point(500, 500)};

    private final int DEFAULT_COLOR = Color.BLACK;
    private final int STROKE_WIDTH = 10;

    private int mainColor;
    private int finderPatternColor;
    private Point center = new Point(300, 300);
    private Bitmap logo;

    public CodeRenderer() {
        this(-1, -1);
    }

    public CodeRenderer(int mainColor) {
        this(mainColor, -1);
    }

    public CodeRenderer(int mainColor, int finderPatternColor) {
        if (mainColor == -1)
            mainColor = DEFAULT_COLOR;
        if (finderPatternColor == -1)
            finderPatternColor = DEFAULT_COLOR;
        this.mainColor = mainColor;
        this.finderPatternColor = finderPatternColor;
    }

    public void setLogo(Bitmap bitmap) {
        logo = bitmap;
    }

    public Bitmap render(Code code) {
        Bitmap bitmap = Bitmap.createBitmap(CODE_SIZE, CODE_SIZE, Bitmap.Config.ARGB_4444);
        Canvas canvas = new Canvas();
        canvas.setBitmap(bitmap);

        renderPattern(canvas);
        renderData(code, canvas);
        return bitmap;
    }

    private void renderPattern(Canvas canvas) {
        Paint paint = newPaint(finderPatternColor, STROKE_WIDTH);

        int finderPatternRadius = FINDER_PATTERN_SIZE / 2;
        Point[] patternsPosition = FINDER_PATTERN_POSITION;

        //三个定位符
        drawFinderPattern(canvas, paint, patternsPosition[0], finderPatternRadius);
        drawFinderPattern(canvas, paint, patternsPosition[1], finderPatternRadius);
        drawFinderPattern(canvas, paint, patternsPosition[2], finderPatternRadius);

        //辅助定位符
        paint.setStyle(Paint.Style.FILL_AND_STROKE);
        paint.setColor(Color.argb(0xff, 77, 77, 77));
        canvas.drawCircle(patternsPosition[3].x, patternsPosition[3].y, finderPatternRadius * 2, paint);
    }

    private void renderData(Code code, Canvas canvas) {
        BitArray allBits = new BitArray();
        allBits.appendBitArray(code.getVersionInfoBits());
        allBits.appendBitArray(code.getTypeInfoBits());
        allBits.appendBitArray(new BitArray(9));
        allBits.appendBitArray(code.getMaskedBits());


        Paint paint = newPaint(mainColor, STROKE_WIDTH);

        int bitIndex = 0;
        int totalPoints;
        int currentCircleRadius;
        int startPointIndex, endPointIndex;
        boolean circleStartPoint = false;
        for (int i = 0; i < NUMBER_OF_TURNS; i++) {
            totalPoints = PER_CIRCLE_POINT_COUNT[i];
            currentCircleRadius = INIT_RADIUS + i * 20;
            startPointIndex = endPointIndex = -1;
            for (int j = 0; j < totalPoints && bitIndex < allBits.getSize(); j++) {
                if (needSkipPoint(i, j)) {
                    if (startPointIndex == -1 || endPointIndex == -1)
                        continue;
                    drawData(startPointIndex, endPointIndex, totalPoints, canvas, currentCircleRadius, paint);
                    startPointIndex = endPointIndex = -1;
                    continue;
                }
                boolean bit = allBits.get(bitIndex);
                if (j == 0)
                    circleStartPoint = bit;
                bitIndex++;
                if (bit) {
                    if (startPointIndex == -1) {
                        endPointIndex = startPointIndex = j;
                    } else {
                        endPointIndex = j;
                    }
                    if (j == totalPoints - 1) {
                        // TODO: 2017/10/13 一圈的结尾，并且最后一位数据刚好是黑色，则绘制
                        if (circleStartPoint && endPointIndex == j)
                            endPointIndex++;
                        drawData(startPointIndex, endPointIndex, totalPoints, canvas, currentCircleRadius, paint);
                        startPointIndex = endPointIndex = -1;
                    }
                } else {
                    if (startPointIndex == -1 || endPointIndex == -1)
                        continue;
                    if (j == totalPoints - 1 && circleStartPoint && endPointIndex == j)
                        endPointIndex++;
                    drawData(startPointIndex, endPointIndex, totalPoints, canvas, currentCircleRadius, paint);
                    startPointIndex = endPointIndex = -1;
                }
            }
        }
    }

    private void drawData(int startPointIndex, int endPointIndex, int totalPoints, Canvas canvas, int radius, Paint paint) {
        if (startPointIndex != endPointIndex) {
            drawDataToArc(startPointIndex, endPointIndex, totalPoints, canvas, radius, paint);
        } else {
            drawDataToPoint(startPointIndex, totalPoints, canvas, radius, paint);
        }
    }

    private void drawDataToArc(int startPointIndex, int endPointIndex, int totalPoints, Canvas canvas, int radius, Paint paint) {
        int left, top, right, bottom;
        left = top = center.x - radius;
        right = bottom = center.x + radius;
        RectF rectF = new RectF(left, top, right, bottom);
        float pointAngle = 360f / totalPoints;
        float startAngle = pointAngle * startPointIndex - 90;
        float swapAngle = (endPointIndex - startPointIndex) * pointAngle;
        canvas.drawArc(rectF, startAngle, swapAngle, false, paint);
    }

    private void drawDataToPoint(int pointIndex, int totalPoints, Canvas canvas, int radius, Paint paint) {
        Point point = calculatePointPosition(pointIndex, totalPoints, radius);
        canvas.drawPoint(point.x, point.y, paint);
    }

    private void drawLogo(Canvas canvas, Paint paint) {
        canvas.drawBitmap(logo, 232, 232, paint);
    }

    private Point calculatePointPosition(int currPointIndex, int totalPoints, int radius) {
        double angle = currPointIndex * 2 * Math.PI / totalPoints;
        double sin = Math.sin(angle);
        double cos = Math.cos(angle);
        Point pCenter = new Point();
        pCenter.x = (int) (center.x + radius * sin);
        pCenter.y = (int) (center.y - radius * cos);
        return pCenter;
    }

    private boolean needSkipPoint(int circleIndex, int pointIndex) {
        if (circleIndex < 4)
            return false;
        switch (circleIndex) {
            case 4:
                return pointIndex >= 43 && pointIndex < 50;
            case 5:
                return (pointIndex >= 46 && pointIndex < 55) || (pointIndex >= 14 && pointIndex < 20) || (pointIndex >= 80 && pointIndex < 87) || (pointIndex >= 114 && pointIndex < 120);
            case 6:
                return (pointIndex >= 49 && pointIndex < 59) || (pointIndex >= 15 && pointIndex < 22) || (pointIndex >= 86 && pointIndex < 93) || (pointIndex >= 122 && pointIndex < 129);
            case 7:
                return (pointIndex >= 53 && pointIndex < 64) || (pointIndex >= 16 && pointIndex < 23) || (pointIndex >= 93 && pointIndex < 100) || (pointIndex >= 132 && pointIndex < 138);
            default:
                return false;
        }
    }

    private void drawFinderPattern(Canvas canvas, Paint paint, Point patternCenter, int size) {
        canvas.drawPoint(patternCenter.x, patternCenter.y, paint);
        float strokeWidth = paint.getStrokeWidth();
        paint.setStrokeWidth(8);
        canvas.drawCircle(patternCenter.x, patternCenter.y, size, paint);
        paint.setStrokeWidth(strokeWidth);
    }

    private Paint newPaint(int color, int strokeWidth) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(color);
        paint.setStrokeWidth(strokeWidth);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.ROUND);
        return paint;
    }

    /*------------------------ ------------------------ 另一种绘制方式 ------------------------ ------------------------*/

    private void renderData2(Code code, Canvas canvas) {
        BitArray bitArray = code.getFinalBits();

        Paint paint = newPaint(mainColor, STROKE_WIDTH);

        int bitIndex = 0;
        int totalPoints;
        int currentCircleRadius;
        int startPointIndex, endPointIndex;
        boolean circleStartPoint = false;
        for (int i = 0; i < NUMBER_OF_TURNS; i++) {
            totalPoints = PER_CIRCLE_POINT_COUNT[i];
            currentCircleRadius = INIT_RADIUS + i * 20;
            startPointIndex = endPointIndex = -1;
            List<Point> pixes = pickPix(currentCircleRadius);
            for (int j = 0; j < totalPoints && bitIndex < bitArray.getSize(); j++) {
                if (needSkipPoint(i, j)) {
                    if (startPointIndex == -1 || endPointIndex == -1)
                        continue;
                    drawData2(startPointIndex, endPointIndex, totalPoints, pixes, canvas, currentCircleRadius, paint);
                    startPointIndex = endPointIndex = -1;
                    continue;
                }
                boolean bit = bitArray.get(bitIndex);
                if (j == 0)
                    circleStartPoint = bit;
                bitIndex++;
                if (bit) {
                    if (startPointIndex == -1) {
                        endPointIndex = startPointIndex = j;
                    } else {
                        endPointIndex = j;
                    }
                    if (j == totalPoints - 1) {
                        // TODO: 2017/10/13 一圈的结尾，并且最后一位数据刚好是黑色，则绘制
                        if (circleStartPoint)
                            endPointIndex++;
                        drawData2(startPointIndex, endPointIndex, totalPoints, pixes, canvas, currentCircleRadius, paint);
                        startPointIndex = endPointIndex = -1;
                    }
                } else {
                    if (startPointIndex == -1 || endPointIndex == -1)
                        continue;
                    if (j == totalPoints - 1 && circleStartPoint)
                        endPointIndex++;
                    drawData2(startPointIndex, endPointIndex, totalPoints, pixes, canvas, currentCircleRadius, paint);
                    startPointIndex = endPointIndex = -1;
                }
            }
        }
    }

    private void drawData2(int startPointIndex, int endPointIndex, int totalPoints, List<Point> pixes, Canvas canvas, int radius, Paint paint) {
        if (startPointIndex != endPointIndex) {
            drawDataToArc(startPointIndex, endPointIndex, totalPoints, canvas, radius, paint);
        } else {
            drawDataToPoint2(startPointIndex, totalPoints, pixes, canvas, radius, paint);
        }
    }

    private void drawDataToPoint2(int pointIndex, int totalPoints, List<Point> pixes, Canvas canvas, int radius, Paint paint) {
        Point point = getIndexPoint(pointIndex, pixes, totalPoints);
        canvas.drawPoint(point.x, point.y, paint);
    }

    private Point getIndexPoint(int index, List<Point> pixes, int totalPoints) {
        float pixesOnePoint = (float) (pixes.size() * 1.0 / totalPoints);
        return pixes.get((int) (index * pixesOnePoint));
    }

    /**
     * 提取bitMatrix中一圈像素
     */
    private List<Point> pickPix(int radius) {
        List<Point> points = bresenhamCircle(center.x, center.y, radius);
        List<Point> sortedPoints = new ArrayList<>();
        int segSize = points.size() / 8;
        for (int i = 0; i < 8; i++) {
            if (i % 2 == 0) {
                for (int j = 0; j < segSize; j++) {
                    sortedPoints.add(points.get(i + j * 8));
                }
            } else {
                for (int j = segSize - 1; j >= 0; j--) {
                    sortedPoints.add(points.get(i + j * 8));
                }
            }
        }
        return sortedPoints;
    }

    /**
     * bresenham画圆算法
     */
    private List<Point> bresenhamCircle(int centerX, int centerY, int raidus) {
        List<Point> points = new ArrayList<>();
        int x = 0;
        int y = raidus;
        int d = 3 - 2 * raidus;
        circlePlot(centerX, centerY, x, y, points);
        while (x <= y) {
            if (d < 0) {
                d = d + 4 * x + 6;
            } else {
                d = d + 4 * (x - y) + 10;
                y--;
            }
            x++;
            circlePlot(centerX, centerY, x, y, points);
        }
        return points;
    }

    private void circlePlot(int centerX, int centerY, int x, int y, List<Point> points) {

        points.add(new Point(centerX + x, centerY - y));
        points.add(new Point(centerX + y, centerY - x));
        points.add(new Point(centerX + y, centerY + x));
        points.add(new Point(centerX + x, centerY + y));
        points.add(new Point(centerX - x, centerY + y));
        points.add(new Point(centerX - y, centerY + x));
        points.add(new Point(centerX - y, centerY - x));
        points.add(new Point(centerX - x, centerY - y));

    }

}